package com.fanml.service;

import com.alibaba.fastjson.JSONObject;
import com.fanml.utils.FileUtil;
import com.fanml.entty.FileInfo;
import com.fanml.entty.constant.ConstantValue;
import com.fanml.entty.enums.MsgTypeEnum;
import com.fanml.entty.netty.Message;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @title: NettyClientDownloadHandle
 * @Author fanml
 * @describe: //TODO
 * @Date: 2021/7/4 17:28
 * @Version 1.0
 */
public class NettyClientDownloadHandle extends ChannelInboundHandlerAdapter {

    private ChannelHandlerContext ctx;
    private String fileName;//要下载的文件名
    private String md5FileName;//要下载的文件名md5
    private File localFile;//下载完成后的文件
    private int receiveBlock;//当前接收的文件块
    private long fileLength;//下载文件的大小

    /**
     * @param fileName   文件名带后缀
     * @param fileLength 文件大小
     * @return
     * @throws
     * @author fanml
     * @date 2021/7/4 17:24
     */
    public NettyClientDownloadHandle(String fileName, long fileLength) {
        //初始化一些变量信息
        this.fileName = fileName;
        this.fileLength = fileLength;
        md5FileName = DigestUtils.md5Hex(fileName + fileLength);
        localFile = new File(ConstantValue.DOWNLOAD_FILE_PATH + md5FileName, fileName);
        String md5LocalBlockDir = ConstantValue.DOWNLOAD_FILE_BLOCK_PATH + md5FileName;
        if (!new File(md5LocalBlockDir).exists()) {
            new File(md5LocalBlockDir).mkdirs();
        }
        //记录已下载的文件块号
        File[] files = new File(md5LocalBlockDir).listFiles();
        receiveBlock = files.length;
        if (receiveBlock > 0) {
            try {
                FileUtils.deleteDirectory(files[receiveBlock - 1]);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            receiveBlock = 0;
        }
    }

    /**
     * @param ctx
     * @return
     * @throws Exception
     * @author fanml
     * @date 2021/7/4 17:29
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //建立连接的时候发送要下载的文件名
        FileInfo fileInfo = new FileInfo(fileName, fileLength, 0, receiveBlock, null);
        String s = JSONObject.toJSONString(fileInfo);
        Message message = new Message("", MsgTypeEnum.DOWNlOAD, s.getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(message);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        if (!(msg instanceof Message)) {
            return;
        }
        Message message = null;
        try {
            message = (Message) msg;
            if (MsgTypeEnum.DOWNlOAD == message.getMsgTypeEnum()) {
                //下载文件
                byte[] msgBody = message.getMsgBody();
                FileInfo fileInfo = JSONObject.parseObject(new String(msgBody, CharsetUtil.UTF_8), FileInfo.class);
                int blockIndex = fileInfo.getBlockIndex();//文件块号
                int blockCount = fileInfo.getBlockCount();//文件总块数
                byte[] content = fileInfo.getContent();//文件内容
                String md5Content = fileInfo.getMd5Content();
                if (content != null) {
                    if (DigestUtils.md5Hex(content).equals(md5Content)) {
                        //校验文件合法性
                        System.out.println("文件" + fileName + " 下载中" + blockIndex + "/" + blockCount);
                        File file = new File(ConstantValue.DOWNLOAD_FILE_BLOCK_PATH + md5FileName + File.separator + blockIndex, fileName + "." + String.format("%0" + String.valueOf(blockCount).length() + "d", blockIndex) + ".part");
                        if (!file.exists()) {
                            new File(file.getParent()).mkdirs();
                        }
                        //保存文件块
                        FileChannel channel = new FileOutputStream(file).getChannel();
                        ByteBuffer buffer = ByteBuffer.allocate(content.length);
                        buffer.put(content);
                        buffer.flip();
                        channel.write(buffer);
                        channel.close();
                        buffer.clear();
                        receiveBlock = blockIndex;
                        if (receiveBlock != blockCount) {
                            fileInfo.setContent(null);
                            fileInfo.setMd5Content(null);
                            fileInfo.setBlockIndex(blockIndex + 1);//下次接收的块号
                            String s = JSONObject.toJSONString(fileInfo);
                            ctx.writeAndFlush(new Message("", MsgTypeEnum.DOWNlOAD, s.getBytes(CharsetUtil.UTF_8)));
                            return;
                        }
                        //文件接收完成进行文件合并
                        FileUtil.mergeDirFile(ConstantValue.DOWNLOAD_FILE_BLOCK_PATH + md5FileName, localFile.getParent());
                        System.out.println("文件下载完成: " + localFile.getPath());
                        ctx.close();
                    } else {
                        System.out.println("传输文件已损坏,块号：" + blockIndex);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(message);
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("发生异常了----------------------------");
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
    }
}
